/*
 * mftgtexture.cpp
 *
 *  Created on: 10/08/2012
 *      Author: cvan680
 */

#include "mftgtexture.h"
#include "tgt/textureunit.h"
#include "modules/flowreen/utils/flowmath.h"
#include "modules/flowreen/datastructures/volumeflow3d.h"
#include "modules/flowreen/datastructures/flow2d.h"
#include "modules/flowreen/datastructures/flow3d.h"
#include "modules/flowreen/datastructures/simpletexture.h"

namespace voreen {

/**
 * TODO
 *
 * Figure out: do we want to do
 * 	a) every technique pixel-by-pixel, or
 * 	b) every technique global, or
 * 	c) hybrid; e.g. one technique global, the other pixel-by-pixel and only afterwards integrate both
 */

/**
 * borrowed from SimpleTexture:
 *  data_
 *  dimensions_
 *
 * borrowed from StreamlineTexture:
 * 	counter_
 */

/*************************************/
/*   CONSTRUCTORS AND DESTRUCTORS    */
/*************************************/

MFTGTexture::MFTGTexture(const tgt::ivec2 dimensions)
    : dimensions_(dimensions.x, dimensions.y, 1)
    , numElements_(dimensions_.x * dimensions_.y)
	, data_(new float[numElements_ * NUM_CHANNELS])
	, counter_(new int[numElements_])
{
    memset(data_, 0, sizeof(float) * numElements_ * NUM_CHANNELS);
    memset(counter_, 0, sizeof(int) * numElements_);
}

MFTGTexture::MFTGTexture(const tgt::ivec3& dimensions)
	: dimensions_(dimensions.x, dimensions.y, dimensions.z)
	, numElements_(dimensions_.x * dimensions_.y * dimensions.z)
	, data_(new float[numElements_ * NUM_CHANNELS])
	, counter_(new int[numElements_])
{
    memset(data_, 0, sizeof(float) * numElements_ * NUM_CHANNELS);
    memset(counter_, 0, sizeof(int) * numElements_);
}


MFTGTexture::~MFTGTexture() {
	// TODO do we need the autoFree check here like in SimpleTexture??
    free();
}

void MFTGTexture::free() {
    delete [] data_;
    delete [] counter_;
}


void MFTGTexture::loadTextureGrammar(string filename){
	//FIXME do some magic where a TextureGrammar object
	//		is created with the given texturegrammar file
}

/* load volumes in this thing */

void MFTGTexture::loadVolume(const VolumeBase* volume){
	volumes_.push_back(volume); // dereference pointer.. are we doing this correctly? so confusing..
}

void MFTGTexture::loadVolumeCollection(VolumeCollection* collection){
	int num_volumes = ((collection != 0) ? static_cast<int>(collection->size()) : 0);\
	if(num_volumes == 0){ return; }

	// FIXME how the hell do I do this...
	// FlowreenAdapter fAdapter = new FlowreenAdapter();

	for(int i=0; i<num_volumes; i++){
		// TODO find out if it is a flowvolume; if so, run it through flowreenadapter.

		//volumes_.push_back( collection->at(i) ); // TODO make sure THIS is correct.. lol
		cout << "lolly FIXME";
	}
	// FIXME do something about flow volumes (flowreenadapter and stuff)
}

void MFTGTexture::run(){
	/* FIXME
	 * 1. make sure we have a valid texturegrammar, and valid volumes
	 * 2. interpret texturegrammar, and run it to determine what texture synthesizing
	 *    operations need to be done (consider global and/or local)
	 * 3. write results to _data. this can then be picked up by getData() method.
	 */
}

/**
 * Only one volume, performs original LIC on volume and writes values to data_.
 */
void MFTGTexture::fastLIC(const Flow3D& flow3D1, const Flow3D& flow3D2) {
	// FIXME get flowvolumes from loaded volumes instead
/*
	// find two flowvolumes!
	int flowvols_found = 0;
	int index = 0;
	VolumeFlow3D* vol1;
	VolumeFlow3D* vol2;
	while(flowvols_found < 2 && index < volumes_.size()){
		VolumeFlow3D* temp = checkVolumeHandleForFlowData<VolumeFlow3D>(volumes_[index], false);
		if(temp){
			if(flowvols_found == 0){ vol1 = temp; }
			else{ vol2 = temp; }
			++flowvols_found;
		}
		++index;
	}

	if(!(flowvols_found == 2)){ return; }

    const Flow3D& flow3D1 = vol1->getFlow3D();
    const Flow3D& flow3D2 = vol2->getFlow3D();
*/
	// FIXME all capitalized variables need to be provided by external method probably (OR texturegrammar)

	cout << "\n\nTEST 1";

	int SLICENO = 1;
	tgt::ivec3 PERMUTATION(0,1,2);
	const int TEXTURESCALING = 1;
	const int MAXKERNELSIZE = 4;
	bool USEADAPTIVEKERNELSIZE = false;
	const int SAMPLING = 30;
	const tgt::vec2& THRESHOLDS(flow3D1.maxMagnitude_ * tgt::vec2(0.0f, 100.0f) / 100.0f);
//		    tgt::vec2 thresholds(flow.maxMagnitude_ * thresholdProp_.get() / 100.0f); // this is how its done when using parameter

    Flow2D flow2D1 = flow3D1.extractSlice(PERMUTATION, SLICENO);
    Flow2D flow2D2 = flow3D2.extractSlice(PERMUTATION, SLICENO);

    SimpleTexture<float> noiseTexture1(flow2D1.dimensions_, true);
    noiseTexture1.createWhiteNoise();

	cout << "\nTEST 2";

	const tgt::ivec2 inputTexSize(noiseTexture1.getDimensions().xy());
    if (flow2D1.dimensions_ != inputTexSize || flow2D2.dimensions_ != inputTexSize)
        return;

	cout << "\nTEST 3";

    // FIXME this.dimensions_ should already have been set with texturescaling!!

    //const tgt::ivec2& outputTexSize(inputTexSize * static_cast<int>(TEXTURESCALING));
    const tgt::ivec2& outputTexSize = this->getDimensions().xy();
    const int delta = static_cast<int>(SAMPLING);
    const float stepSize = (0.5f / TEXTURESCALING);
    const float length = static_cast<float>(tgt::max(flow2D1.dimensions_)) * 1.10f;

	cout << "\nTEST 4";

    int numStreamlines = 0;
    for (int y = 0; y < outputTexSize.y; y += delta) {
        for (int x = 0; x < outputTexSize.x; x += delta) {
            tgt::ivec2 ir0(x, y);
            int px = x*y + x;
            //if (this[ir0].counter_ > 0)
            if (this[px].counter_ > 0)
                continue;

            // get the coordinates of the pixel in the input texture which corresponds
            // to this position in the output texture and calculate its position within
            // the flow.
            //
            tgt::ivec2 r0Input(ir0 / static_cast<int>(TEXTURESCALING));
            tgt::ivec2 errorInput(0, 0);
            tgt::vec2 r0 = flow2D1.slicePosToFlowPos(r0Input, inputTexSize, &errorInput);
            const tgt::vec2 v = flow2D1.lookupFlow(r0);

            if (v == tgt::vec2::zero)
                continue;

            // start streamline computation
            //
            int indexR0 = 0;
            std::deque<tgt::vec2> streamlineD =
                FlowMath::computeStreamlineRungeKutta(flow2D1, r0, length, stepSize, &indexR0, THRESHOLDS);
            if (streamlineD.size() <= 1)
                continue;

            // also determine the round-off error which occurs if the flow positions was
            // converted back directly to the coordinates of the output textures.
            //
            tgt::ivec2 errorOutput(0, 0);
            flow2D1.slicePosToFlowPos(ir0, outputTexSize, &errorOutput);

            ++numStreamlines;
            std::vector<tgt::vec2> streamline = FlowMath::dequeToVector(streamlineD);

            // copy the streamline for second coordinate conversion
            //
            std::vector<tgt::vec2> streamlineCopy(streamline);

            // convert the streamline into dimensions of the input texture
            //
            std::vector<tgt::ivec2> streamlineInput =
                flow2D1.flowPosToSlicePos(streamline, inputTexSize, errorInput);

            // also convert the streamline into dimensions of the output texture
            //
            std::vector<tgt::ivec2> streamlineOutput =
            	flow2D1.flowPosToSlicePos(streamlineCopy, outputTexSize, errorOutput);

            // calculate initial intensity for the starting pixel
            //
            int L = MAXKERNELSIZE;
            if (USEADAPTIVEKERNELSIZE == true)
                L = static_cast<int>(tgt::round(MAXKERNELSIZE * (tgt::length(v) / THRESHOLDS.y)));
            const float k = 1.0f / (((2 * L) + 1));




//            float intensity0 = k * fastLICIntensity(noiseTexture1, indexR0, L, streamlineInput); /* FIXME implement fastlicintensity method */
            float intensity0 = k * fastLICIntensity();



            // determine the affected pixel in the output texture and add the
            // initial intensity
            //
            tgt::ivec2& outputTexCoord = streamlineOutput[indexR0];
            int pixel = outputTexCoord.y * outputTexSize.x + outputTexCoord.x;
            this->data_[pixel*NUM_CHANNELS + 0] += intensity0; //T(intensity0);
            this->data_[pixel*NUM_CHANNELS + 3] += intensity0; //T(intensity0);
            /*
            this[outputTexCoord].r_ += T(intensity0); // FIXME why not access r_ and a_, maar wel counter_??
            this[outputTexCoord].a_ += T(intensity0);
            */
            //outputTexture[outputTexCoord].elem_ += T(intensity0);
            //++(this[outputTexCoord].counter_);
            ++(this[pixel].counter_);

            // trace streamline in forward direction and update intensity
            float intensity = intensity0;
            int left = indexR0 + L + 1;
            int right = indexR0 - L;
            const int numPoints = static_cast<int>(streamlineInput.size());

            for (int i = (indexR0 + 1); i < numPoints; ++i, ++left, ++right) {
                int l = (left >= numPoints) ? (numPoints - 1) : left;
                int r = (right <= 0) ? 0 : right;

                intensity += (noiseTexture1[streamlineInput[l]] - noiseTexture1[streamlineInput[r]]) * k;

                outputTexCoord = streamlineOutput[i];
                /*
                this[outputTexCoord].r_ += T(intensity);
                this[outputTexCoord].a_ += T(intensity);
                */
                pixel = outputTexCoord.y * outputTexSize.x + outputTexCoord.x;
				this->data_[pixel*NUM_CHANNELS + 0] += intensity; //T(intensity);
				this->data_[pixel*NUM_CHANNELS + 3] += intensity; //T(intensity);
                //++(this[outputTexCoord].counter_);
				++(this[pixel].counter_);
            }   // for (i

            // trace streamline in backward direction and update intensity
            //
            intensity = intensity0;
            left = indexR0 - L - 1;
            right = indexR0 + L;
            for (int i = (indexR0 - 1); i >= 0; --i, --left, --right) {
                int l = (left <= 0) ? 0 : left;
                int r = (right >= numPoints) ? (numPoints - 1) : right;

                intensity += (noiseTexture1[streamlineInput[l]] - noiseTexture1[streamlineInput[r]]) * k;

                outputTexCoord = streamlineOutput[i];
                /*
                this[outputTexCoord].r_ += T(intensity);
                this[outputTexCoord].a_ += T(intensity);
                */
                pixel = outputTexCoord.y * outputTexSize.x + outputTexCoord.x;
				this->data_[pixel*NUM_CHANNELS + 0] += intensity; //T(intensity);
				this->data_[pixel*NUM_CHANNELS + 3] += intensity; //T(intensity);
                //++(this[outputTexCoord].counter_);
				++(this[pixel].counter_);
            }   // for (i
        }   // for (x
    }   // for (y

	cout << "\nTEST 5";

    int unhitPixels = 0;
    const int numPixels = this->numElements_;
    for (int i = 0; i < numPixels; ++i) {
        if (*this[i].counter_ > 1){
        	/*
            this[i].r_ /= static_cast<unsigned char>(this[i].counter_);
            this[i].a_ /= static_cast<unsigned char>(this[i].counter_);
            */
        	/*
        	this->data_[i*NUM_CHANNELS + 0] /= static_cast<unsigned char>(this[i].counter_);
        	this->data_[i*NUM_CHANNELS + 3] /= static_cast<unsigned char>(this[i].counter_);
        	*/
        	this->data_[i*NUM_CHANNELS + 0] /= *this[i].counter_;
			this->data_[i*NUM_CHANNELS + 3] /= *this[i].counter_;
        }else
            if (*this[i].counter_ <= 0)
                ++unhitPixels;
    }

	cout << "\nTEST 6";
    /* TODO put this line back in
    std::cout << "# streamlines = " << numStreamlines << ", # unhit pixels = " << unhitPixels
        << " (" << static_cast<float>(100 * unhitPixels) / static_cast<float>(numPixels) << " %)\n";
        */

    /*
    T* result = new T[numPixels];
    memcpy(result, outputTexture.getData(), sizeof(T) * numPixels);
    return result;

*/




}

/**
 * Two (or more??) volumes LIC.. perhaps parameterize which or how many volumes
 * we want to use?? or do we just use the first 2, or the first x?
 */
void MFTGTexture::multiLIC(){

}

/*
float MFTGTexture::fastLICIntensity( const SimpleTexture<float>& inputTexture,
                                     const int indexR0,
                                     const int kernelSize,
                                     const std::vector<tgt::ivec2>& streamline )*/
float MFTGTexture::fastLICIntensity()
{
	/*
    float intensity = inputTexture[streamline[indexR0]];
    for (int n = 1; n <= kernelSize; ++n) {
        if ((indexR0 + n) < streamline.size())
            intensity += inputTexture[streamline[indexR0 + n]];

        if ((indexR0 - n) >= 0)
            intensity += inputTexture[streamline[indexR0 - n]];
    }
    return intensity;
    */
	return 0.2f;
}

/**
 * This function takes a vector field and a scalar field, and
 * applies LIC on the vector field, and colourmaps the scalar
 * field using a shader that takes the LIC texture in account.
 */
tgt::Texture* MFTGTexture::colourLIC() {

	return 0;
}


tgt::Texture* MFTGTexture::getTexture() {

	return new tgt::Texture(reinterpret_cast<GLubyte*>(this->data_),
		tgt::ivec3(this->getDimensions().xy(), 1), GL_RGBA, GL_RGBA,
		GL_FLOAT, tgt::Texture::NEAREST);
}


/**
 * Generates a texture with desired dimensions from a given slice of a scalar volume.
 *
 * volram				the original scalar volume
 * sliceID				which slice do we want
 * textureDimensions	the desired texture dimensions
 * scaling				the scaling that is applied to the volume size to get the scaling (use 1/scaling as steps over volume)
 */
tgt::Texture* MFTGTexture::scalarSliceToIntensityTexture( const VolumeRAM* volram, int sliceID, tgt::ivec2 textureDimensions,
														  int scaling, int alignment ){

	// http://voreen.uni-muenster.de/?q=node/30
	// can do getRepresentation<VolumeGL>() resulting in OpenGL Texture,
	// however this may result in too large texture since we need to think about scaling??
// tgt::Texture* sliceTex = new tgt::Texture(tgt::ivec3(textureDimensions, 1), GL_LUMINANCE, GL_LUMINANCE, GL_FLOAT);
// sliceTex->texel<float>(x, y) = volram->getVoxelNormalizedLinear( tgt::vec3(sliceID, (float)(x/scaling), y/scaling) );

	float* intensities = scalarSliceToIntensity(volram, sliceID, textureDimensions, scaling, alignment);
	return new tgt::Texture(reinterpret_cast<GLubyte*>(intensities),
		tgt::ivec3(textureDimensions, 1), GL_LUMINANCE, GL_LUMINANCE,
		GL_FLOAT, tgt::Texture::NEAREST);
}




float* MFTGTexture::scalarSliceToIntensity( const VolumeRAM* volram,
											int sliceID,
											tgt::ivec2 textureDimensions,
											int scaling,
											int alignment ){

	int numPixels = textureDimensions.x * textureDimensions.y;
	float* result = new float[numPixels];
    memset(result, 0, sizeof(float) * numPixels);

	switch (alignment) {
	case PLANE_ZY:
		{
			for (size_t y=0; y<static_cast<size_t>(textureDimensions.y); y++) {
				for (size_t x=0; x<static_cast<size_t>(textureDimensions.x); x++) {
					result[textureDimensions.x*y + x] = volram->getVoxelNormalizedLinear( tgt::vec3(sliceID, (float)(x/scaling), y/scaling) );
				}
			}
		}
		break;
	case PLANE_XZ:
		{
			for (size_t y=0; y<static_cast<size_t>(textureDimensions.y); y++) {
				for (size_t x=0; x<static_cast<size_t>(textureDimensions.x); x++) {
					result[textureDimensions.x*y + x] = volram->getVoxelNormalizedLinear( tgt::vec3((float)(x/scaling), sliceID, y/scaling) );
				}
			}
		}
		break;
	case PLANE_XY:
		{
			for (size_t y=0; y<static_cast<size_t>(textureDimensions.y); y++) {
				for (size_t x=0; x<static_cast<size_t>(textureDimensions.x); x++) {
					result[textureDimensions.x*y + x] = volram->getVoxelNormalizedLinear( tgt::vec3((float)(x/scaling), (float)(y/scaling), sliceID) );
				}
			}
		}
		break;
	default:
		tgtAssert(false, "invalid slice alignment");
		break;
	}

	std::cout << "\nprint 3.2";

	return result;
}

/*************************************/
/* DATASTRUCTURES AND HELPER METHDOS */ //see SimpleTexture
/*************************************/

const MFTGTexture::MFTGTextureElement MFTGTexture::operator[](int index) const {
    if (index >= numElements_)
        index = numElements_ - 1;
    return MFTGTexture::MFTGTextureElement( data_[index*NUM_CHANNELS+0],
    						   	   	   	   	   data_[index*NUM_CHANNELS+1],
    						   	   	   	   	   data_[index*NUM_CHANNELS+2],
    						   	   	   	   	   data_[index*NUM_CHANNELS+3], counter_[index] );
}

const MFTGTexture::MFTGTextureElement MFTGTexture::operator[](const tgt::ivec2& position) const {
    int index = positionToIndex(position);
    return operator[](index);
}

const MFTGTexture::MFTGTextureElement MFTGTexture::operator[](const tgt::ivec3& position) const {
    int index = positionToIndex(position);
    return operator[](index);
}

MFTGTexture::MFTGTextureElement MFTGTexture::operator[](int index) {
    if (index >= numElements_)
        index = numElements_ - 1;
    return MFTGTexture::MFTGTextureElement( data_[index*NUM_CHANNELS+0],
    						   	   	   	   	   data_[index*NUM_CHANNELS+1],
    						   	   	   	   	   data_[index*NUM_CHANNELS+2],
    						   	   	   	   	   data_[index*NUM_CHANNELS+3], counter_[index] );
}

MFTGTexture::MFTGTextureElement MFTGTexture::operator[](const tgt::ivec2& position) {
    int index = positionToIndex(position);
    return operator[](index);
}

MFTGTexture::MFTGTextureElement MFTGTexture::operator[](const tgt::ivec3& position) {
    int index = positionToIndex(position);
    return operator[](index);
}

/* helper methods to access the index of a certain 2d or 3d position */
int MFTGTexture::positionToIndex(const tgt::ivec2& position) const {
    return static_cast<int>(position.y * dimensions_.x + position.x);
}

int MFTGTexture::positionToIndex(const tgt::ivec3& position) const {
    if (dimensions_.z <= 1)
        return static_cast<int>((position.y * dimensions_.x) + position.x);

    return static_cast<int>((position.z * dimensions_.y * dimensions_.x)
        + (position.y * dimensions_.x) + position.x);
}


}

